package com.qiguliuxing.dts.core.annotation.aoplog;

import com.alibaba.fastjson.JSONObject;
import com.qiguliuxing.dts.common.constant.LogParamsConsts;
import com.qiguliuxing.dts.common.exception.BizException;
import com.qiguliuxing.dts.common.util.IpUtils;
import com.qiguliuxing.dts.core.redis.RedisServiceUtil;
import com.qiguliuxing.dts.db.dao.DtsManagerLogMapper;
import com.qiguliuxing.dts.db.domain.DtsAdmin;
import com.qiguliuxing.dts.db.domain.DtsManagerLogWithBLOBs;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;

/**
 * 切面日志定义与处理
 * 支持不同的profile环境 及参数定义打印日志参数的配置
 * @author  qiguliuxing
 * @since 1.0.0
 */
@Aspect
@Component
@Profile({"dev", "test", "demo", "prod"})
public class WebLogAspect {
    private final static Logger logger = LoggerFactory.getLogger(WebLogAspect.class);

    @Resource
    private DtsManagerLogMapper managerLogMapper;

    /**
     * 以自定义 @WebLog 注解为切点
     */
    @Pointcut("@annotation(com.qiguliuxing.dts.core.annotation.aoplog.WebLog)")
    public void webLog() {}

    /**
     * 环绕
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

        long startTime = System.currentTimeMillis(); // 记录请求时间
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        WebLog webLog = getWeblogAnnotation(proceedingJoinPoint);
        // 获取切面注解不需要打印的参数
        String[] excludes = webLog.excludes();
        // 获取日志参数
        String methodDescription = webLog.description();
        String classMethod = proceedingJoinPoint.getSignature().getDeclaringTypeName() + "." + proceedingJoinPoint.getSignature().getName();
        String sourceIp = IpUtils.getIpAddr(request);
        String requestUrl = request.getRequestURL().toString();
        String requestMethod = request.getMethod();
        String requestBody = JSONObject.toJSONString(proceedingJoinPoint.getArgs());
        // 打印请求相关日志参数
        StringBuilder requestInfo = new StringBuilder();
        requestInfo.append("【请求开始】");
        requestInfo.append("接口描述:" + methodDescription);
        // 打印全路径以及执行方法
        if(!Arrays.asList(excludes).contains(LogParamsConsts.CLASS_METHOD)) {
            requestInfo.append(", 类.方法:" + classMethod);
        }
        // 打印请求的IP
        if(!Arrays.asList(excludes).contains(LogParamsConsts.IP)) {
            requestInfo.append(", 来源IP:" + sourceIp);
        }
        // 打印URL信息
        if(!Arrays.asList(excludes).contains(LogParamsConsts.URL)) {
            requestInfo.append(", 请求URL:" + requestUrl);
        }
        // 打印 Http method
        if(!Arrays.asList(excludes).contains(LogParamsConsts.METHOD)) {
            requestInfo.append(", 请求方式:[" + requestMethod + "]");
        }
        // 打印请求入参
        if(!Arrays.asList(excludes).contains(LogParamsConsts.REQUEST)) {
            requestInfo.append(", 请求报文:" + requestBody);
        }
        logger.info(requestInfo.toString()); // 打印请求日志

        /**
         * 新增全局演示操作控制
         * 注意,此处代码可根据是否对外公开演示进行调整或注释
         */
        if (webLog.persistence() && isDemoForbidModel()) {
            Subject currentUser = SecurityUtils.getSubject();
            if (currentUser != null && !currentUser.hasRole("聚惠星演示可操作角色")) {
                if (!classMethod.endsWith("login")
                     && !requestUrl.endsWith("auth/login")
                     && !classMethod.endsWith("logout")
                     && ! requestUrl.endsWith("auth/logout")) {
                    logger.warn("非授权账号，无权限操作该功能");
                    throw new BizException(9999, "非授权账号，无权限操作该功能!");
                }
            }
        }

        Object result = proceedingJoinPoint.proceed(); // 执行业务

        // 打印返回相关日志参数
        Long spendTime = System.currentTimeMillis() - startTime;
        String responseBody = JSONObject.toJSONString(result);
        StringBuilder responseInfo = new StringBuilder();
        responseInfo.append("【请求结束】");
        responseInfo.append("接口描述:" + methodDescription);
        responseInfo.append(", 执行耗时:" + spendTime + " ms"); // 执行耗时
        // 打印返回报文
        if(!Arrays.asList(excludes).contains(LogParamsConsts.RESPONSE)) {
            responseInfo.append(", 返回报文:" + responseBody);
        }
        logger.info(responseInfo.toString()); // 打印返回日志

        // 如果需要存储管理日志
        if (webLog.persistence()) {
            CompletableFuture.runAsync(() -> {
                try {
                    DtsAdmin admin = null;
                    Subject currentUser = SecurityUtils.getSubject();
                    if (currentUser != null) {
                        admin = (DtsAdmin) currentUser.getPrincipal();
                    }
                    // 只有管理端才做日志存储，目前只管理后台，商户后台做日志持久化存储
                    Integer adminId = admin == null ? null : admin.getId();
                    String username = admin == null ? null : admin.getUsername();

                    DtsManagerLogWithBLOBs managerLog = new DtsManagerLogWithBLOBs();
                    managerLog.setAddTime(LocalDateTime.now());
                    managerLog.setAdminId(adminId);
                    managerLog.setClassMethod(classMethod);
                    managerLog.setSpendTime(spendTime.intValue());
                    managerLog.setMethodDesc(methodDescription);
                    managerLog.setRequestMethod(requestMethod);
                    managerLog.setRequestUrl(requestUrl);
                    managerLog.setSourceIp(sourceIp);
                    managerLog.setRequestBody(requestBody);
                    managerLog.setResponseBody(responseBody);
                    managerLog.setUsername(username);
                    managerLogMapper.insertSelective(managerLog);
                } catch (Exception e) {
                    logger.error("存储管理端操作日志出错：{}", e.getMessage());
                    e.printStackTrace();
                }
            });
        }
        return result;
    }

    /**
     * 通过反射获取注解实体
     * @param joinPoint
     * @return
     * @throws Exception
     */
    private WebLog getWeblogAnnotation(ProceedingJoinPoint joinPoint) throws Exception {
        WebLog webLog = null;
        String targetName = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        Object[] arguments = joinPoint.getArgs();
        Class targetClass = Class.forName(targetName);
        Method[] methods = targetClass.getMethods();
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                Class[] clazzs = method.getParameterTypes();
                if (clazzs.length == arguments.length) {
                    webLog = method.getAnnotation(WebLog.class);
                    break;
                }
            }
        }
        return webLog;
    }



    /**
     * 新增演示版本禁止修改操作控制
     * 1：演示禁止修改操作
     * 0: 不禁止
     * @return
     */
    private boolean isDemoForbidModel() {
       String demoForbidModelDb = RedisServiceUtil.get("dts.system.demo.forbid.model");
        int demoForbidModel = 0;
        if (demoForbidModelDb != null) {
            demoForbidModel = Integer.parseInt(demoForbidModelDb);
        }

        return demoForbidModel == 1 ? true : false;
    }

}
